//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-07-13     ZhangYihua   first version
//
// Description  : Free Buffer Manager by Single Dimension Array.
//################################################################################

module fbm_sda #(
parameter           BIT_NUM                 = 256,  // BIT_NUM could not be too big because of Fmax, it's recommended not to exceed 128 for low-end device
                                                    // P&R BIT_NUM=256 in Xilinx Artix 7 xc7a200tffg1156-3, Fmax=153MHz
parameter           RLS_OT_CHKEN            = 1'b0, // if RLS_OT_CHKEN==1'b1, be sure that any applied IDX must be released during a period of timer_tick

parameter           CNT_BW                  = $clog2(BIT_NUM+1),
parameter           IDX_BW                  = $clog2(BIT_NUM)
) ( 
input                                       rst_n,
input                                       clk,

input                                       timer_tick,         // cyclic single pulse for overtime checking, interval is longer enough, not used when RLS_OT_CHKEN==1'b0

input                                       apply_en,           // single pulse
output  reg         [IDX_BW-1:0]            apply_idx,          // return latency is 1 from apply_en==1'b1

input                                       rls_en,             // single pulse
input               [IDX_BW-1:0]            rls_idx,            // legal rage [0:BIT_NUM-1], be aligned to rls_en==1'b1

output  reg         [CNT_BW-1:0]            free_cnt,           // legal range [0:BIT_NUM]

input                                       cfg_ini_restart,    // single pulse
output              [5-1:0]                 alm_ilf             // internal logic fault
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam [CNT_BW-1:0] BIT_NUM_V           = BIT_NUM;
localparam [IDX_BW-1:0] IDX_MAX             = BIT_NUM-1;

wire                                        fbb_ce;
wire                                        fbb_exist;
wire                [BIT_NUM-1-1:0]         sp_gnt_pos_nc;
wire                [BIT_NUM-1:0]           sp_gnt;
wire                [BIT_NUM-1:0]           rls_set;
wire                [BIT_NUM-1:0]           apply_clr;
reg                 [BIT_NUM-1:0]           fbb;
wire                [IDX_BW-1:0]            apply_idx_c;
wire                                        ini_err;
wire                                        apply_err;
reg                                         rls_err;
wire                                        cnt_exist;
reg                                         cnt_err;
wire                                        idx_lost;

//################################################################################
// main
//################################################################################

assign fbb_ce = cfg_ini_restart | apply_en | rls_en;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        free_cnt <=`U_DLY BIT_NUM_V;
    end else if (fbb_ce==1'b1) begin
        if (cfg_ini_restart==1'b1)
            free_cnt <=`U_DLY BIT_NUM_V;
        else if ((rls_en==1'b1) && (apply_en==1'b0))
            free_cnt <=`U_DLY free_cnt + 1'd1;
        else if ((rls_en==1'b0) && (apply_en==1'b1))
            free_cnt <=`U_DLY free_cnt - 1'd1;
        else
            ;
    end else
        ;
end

arb_sp #(.PORT_NUM(BIT_NUM)) u_apply_sp ( 
        .req            (fbb            ),	// raw request vector
        .req_exist      (fbb_exist      ),	// logic OR of all reqs
        .gnt_pos        (sp_gnt_pos_nc  ),	// position of current grant
        .gnt            (sp_gnt         )	// immediate grants according to req
);

genvar g;
generate for (g=0; g<BIT_NUM; g=g+1) begin:G_BIT

    assign rls_set[g] = ($unsigned(g)==rls_idx) ? rls_en : 1'b0;

    assign apply_clr[g] = sp_gnt[g] & apply_en;

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            fbb[g] <=`U_DLY 1'b1;
        end else if (fbb_ce==1'b1) begin
            if (cfg_ini_restart==1'b1)
                fbb[g] <=`U_DLY 1'b1;
            else
                fbb[g] <=`U_DLY (fbb[g] | rls_set[g]) & (~apply_clr[g]);
        end else
            ;
    end
    
end endgenerate

onehot2bin #(.OH_BW(BIT_NUM)) u_oh2bin (.oh(sp_gnt), .bin(apply_idx_c));

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        apply_idx <=`U_DLY {IDX_BW{1'b0}};
    end else if (apply_en==1'b1) begin
        apply_idx <=`U_DLY apply_idx_c;
    end else
        ;
end

assign ini_err = cfg_ini_restart & (apply_en | rls_en);
assign apply_err = (free_cnt=={CNT_BW{1'b0}}) ? apply_en : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rls_err <=`U_DLY 1'b0;
    end else if (rls_en==1'b1) begin
        if (rls_idx>IDX_MAX)
            rls_err <=`U_DLY 1'b1;
        else
            rls_err <=`U_DLY (|(rls_set & fbb));
    end else
        rls_err <=`U_DLY 1'b0;
end

assign cnt_exist = |free_cnt;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_err <=`U_DLY 1'b0;
    end else begin
        cnt_err <=`U_DLY cnt_exist ^ fbb_exist;
    end
end

generate if (RLS_OT_CHKEN==1'b1) begin:G_CHK
    reg                                         chk_vld;
    reg                 [IDX_BW-1:0]            chk_idx;
    wire                                        chk_bit_c;
    reg                                         chk_bit;
    reg                                         ot_flag;
    
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            chk_vld <=`U_DLY 1'b0;
            chk_idx <=`U_DLY {IDX_BW{1'b0}};
        end else begin
            if ((chk_idx>IDX_MAX) || (cfg_ini_restart==1'b1)) begin
                chk_vld <=`U_DLY 1'b0;
                chk_idx <=`U_DLY {IDX_BW{1'b0}};
            end else if ((chk_vld==1'b1) && (chk_bit==1'b1)) begin
                chk_vld <=`U_DLY 1'b0;
                chk_idx <=`U_DLY (chk_idx>=IDX_MAX) ? {IDX_BW{1'b0}} : (chk_idx + 1'd1);
            end else if (free_cnt<BIT_NUM_V)
                chk_vld <=`U_DLY 1'b1;
            else
                ;
        end
    end

    n2o #(.PNUM(BIT_NUM), .DWID(1), .SMODE("BINARY")) u_chk_fbb ( 
        .sel        (chk_idx    ),
        .np_dat     (fbb        ),
        .op_dat     (chk_bit_c  )
    );

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            chk_bit <=`U_DLY 1'b0;
        end else begin
            chk_bit <=`U_DLY chk_bit_c;
        end
    end

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            ot_flag <=`U_DLY 1'b0;
        end else begin
            if (chk_vld==1'b0)
                ot_flag <=`U_DLY 1'b0;
            else if (timer_tick==1'b1)
                ot_flag <=`U_DLY 1'b1;
            else
                ;
        end
    end
    assign idx_lost = ot_flag & timer_tick;

end else begin:G_NCHK

    assign idx_lost = 1'b0;

end endgenerate

assign alm_ilf = {idx_lost, ini_err, cnt_err, apply_err, rls_err};

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

a_rls_err: assert property (@(posedge clk) disable iff (!rst_n)
    (rls_err==1'b0)
) else begin
    $error("rls_idx is over IDX_MAX or release a IDX before appling it.");
    $stop;
end

a_apply_err: assert property (@(posedge clk) disable iff (!rst_n)
    (apply_err==1'b0)
) else begin
    $error("apply IDX when any buffer is not free.");
    $stop;
end

a_ini_err: assert property (@(posedge clk) disable iff (!rst_n)
    (ini_err==1'b0)
) else begin
    $error("apply or release during cfg_ini_restart.");
    $stop;
end

a_lost_err: assert property (@(posedge clk) disable iff (!rst_n)
    (RLS_OT_CHKEN==1'b1) |-> (idx_lost==1'b0)
) else begin
    $error("Applied IDX is not released whithin specified period.");
    $stop;
end

// generally define CBB_DEBUG_ON only when debugging CBB
`ifdef CBB_DEBUG_ON

a_cnt_err: assert property (@(posedge clk) disable iff (!rst_n)
    (cnt_err==1'b0)
) else begin
    $error("free_cnt miss match to fbb.");
    $stop;
end

`endif

// synopsys translate_on
`endif

endmodule
